<?php

namespace App\Http\Requests;

use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Support\Facades\Crypt;
use App\Models\Loan;
use App\Models\User;
use App\Models\Book;
use Carbon\Carbon;

class UpdateLoanRequest extends FormRequest {

    /**
     * Determine if the user is authorized to make this request.
     *
     * @return bool
     */
    public function authorize() {
        return true;
    }

    /**
     * Get the validation rules that apply to the request.
     *
     * @return array
     */
    public function rules() {
        return [
            'user_id' => 'required|numeric|integer|exists:users,id',
            'book_id' => 'required|numeric|integer|exists:books,id',
            'start' => 'required|date',
            'end' => 'nullable|date'
        ];
    }

    /**
     * Configure the validator instance.
     *
     * @param  \Illuminate\Validation\Validator  $validator
     * @return void
     */
    public function withValidator($validator) {
        $validator->after(function ($validator) {
            if ($this->hasUserChanged()) {
                if ($this->hasUserLoans()) {
                    $validator->errors()->add('user_id', 'The user has 1 or more '
                            . 'pending loans.');
                }

                if ($this->hasUserPenalties()) {
                    $validator->errors()->add('user_id', 'The user has pending '
                            . 'penalties.');
                }
            }

            if ($this->hasBookChanged()) {
                if ($this->isBookDownloadable()) {
                    $validator->errors()->add('book_id', 'The book is '
                            . 'downloadable.');
                }

                if ($this->hasBookLoan()) {
                    $validator->errors()->add('book_id', 'The book has been '
                            . 'loaned.');
                }
            }

            if ($this->end !== null) {
                if ($this->isEndLower()) {
                    $validator->errors()->add('end', 'The end field is lower '
                            . 'than the start field.');
                }
            }
        });
    }

    /**
     * Check if the user has been changed.
     * 
     * @return bool
     */
    private function hasUserChanged() {
        $loan_id = Crypt::decrypt($this->loan_id);

        return Loan::where('id', $loan_id)
                        ->where('user_id', $this->user_id)
                        ->doesntExist();
    }

    /**
     * Check if the user has pending loans.
     * 
     * @return bool
     */
    private function hasUserLoans() {
        return User::where('id', $this->user_id)
                        ->withCount(['loans' => function ($query) {
                                $query->whereNull('end');
                            }])->having('loans_count', '>=', 1)
                        ->exists();
    }

    /**
     * Check if the user has pending penalties.
     * 
     * @return bool
     */
    private function hasUserPenalties() {
        return User::where('id', $this->user_id)
                        ->whereHas('penalties', function ($query) {
                            $query->where('end', '>',
                                    Carbon::parse($this->start)
                                    ->format('Y-m-d'));
                        })->exists();
    }

    /**
     * Check if the book has been changed.
     * 
     * @return bool
     */
    private function hasBookChanged() {
        $loan_id = Crypt::decrypt($this->loan_id);

        return Loan::where('id', $loan_id)
                        ->where('book_id', $this->book_id)
                        ->doesntExist();
    }

    /**
     * Check if the book is downloadable.
     * 
     * return bool
     */
    private function isBookDownloadable() {
        return Book::where('id', $this->book_id)
                        ->whereNotNull('document')
                        ->exists();
    }

    /**
     * Check if the book has been loaned.
     * 
     * @return bool
     */
    private function hasBookLoan() {
        return Book::where('id', $this->book_id)
                        ->whereHas('loans', function ($query) {
                            $query->whereNull('end');
                        })->exists();
    }

    /**
     * Check if the end date is lower than the start date.
     * 
     * @return bool
     */
    private function isEndLower() {
        return $this->end < $this->start;
    }

}
